//Copyright 2011 New York City 3D Community

//This file is part of New York City 3D.

//New York City 3D is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//New York City 3D is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with New York City 3D.  If not, see <http://www.gnu.org/licenses/>.
package states;

import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.input.FlyByCamera;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.objects.Properties;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.util.SkyFactory;
import java.util.Calendar;
import java.util.HashMap;
import nyc3d.HUD;
import nyc3d.NYC3DApplication;
import nyc3d.NodeInspector;
import nyc3d.StreetInfo;

/**
 * Main state
 */
public class MainState extends AbstractAppState implements ActionListener{
    public static final String MAIN_PAUSE = "Main - Pause";
    private Node rootNode = new Node("MainState Root Node");
    Node rawWorld, world;
    private NYC3DApplication app;
    private HashMap<String, StreetInfo> streetInfoMap;
    private HUD hud;
    DirectionalLight sun;
    private Vector3f lightDir = new Vector3f(0,-1,0); // same as light source
    BasicShadowRenderer bsr;
    protected FlyByCamera flyCam; 
    private final float FLYCAM_SPEED = 200;
    
    private final int STREETLIGHTS_INDEX = 0;
    private final int ROADS_INDEX = 1;
    
    public MainState(NYC3DApplication app){
        this.app = (NYC3DApplication) app;
        
        InputManager inputManager = app.getInputManager();
        
        flyCam = new FlyByCamera(app.getCamera());
        flyCam.setMoveSpeed(FLYCAM_SPEED);
        flyCam.registerWithInput(inputManager);       
        
        hud = new HUD(this.app.getSettings());
        app.getNifty().fromXml("hud.xml", "hud");
        System.out.println("Initializing MainState...");
        rootNode.attachChild(SkyFactory.createSky(app.getAssetManager(), "Scenes/Beach/FullskiesSunset0068.dds", false));
        rawWorld = (Node)app.getAssetManager().loadModel("Models/Brooklyn1/Brooklyn2.blend");
        
        rawWorld.setName("World");
        rawWorld.setShadowMode(ShadowMode.CastAndReceive);
        rootNode.attachChild(rawWorld);
        initWorld(rawWorld);
        bsr = new BasicShadowRenderer(app.getAssetManager(), 256);
        app.getViewPort().addProcessor(bsr);
        rootNode.setShadowMode(ShadowMode.Off);
        initSun();
        app.getCamera().setFrustumFar(5000);
        app.getCamera().setLocation(new Vector3f(0, 100, 0));
        app.getCamera().lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
    }
    
    @Override
    public void initialize(AppStateManager stateManager, Application app){
        super.initialize(stateManager, app); 
        
        InputManager inputManager = app.getInputManager();
        flyCam.setEnabled(true);
        flyCam.registerWithInput(inputManager);
        inputManager.addMapping(MAIN_PAUSE, new KeyTrigger(KeyInput.KEY_ESCAPE));
        inputManager.addListener(this, MAIN_PAUSE);
    }
    
    private void initWorld(Node rawWorld) {
        streetInfoMap = new HashMap<String, StreetInfo>();
        
        world = NodeInspector.buildNode(rootNode, rawWorld);
        
        NodeInspector.inspectNode(0, (Spatial)world);
        populateRoadData(world);
    
    }
    
    private void initSun(){
        /** A white, directional light source */
        sun = new DirectionalLight();
        sun.setDirection(lightDir);
        sun.setColor(ColorRGBA.White.clone().multLocal(1.7f));
        rootNode.addLight(sun);
    
    }
    
    private void populateRoadData(Node world){
        Node roads = (Node)world.getChild(ROADS_INDEX);
        
        for(int i = 0; i < roads.getChildren().size(); i++) {
            for (int j = 0; j < ((Node)roads.getChild(i)).getChildren().size(); j++) {
                for(int k = 0; k < ((Node)((Node)roads.getChild(i)).getChild(j)).getChildren().size(); k++){
                    Spatial roadObj = ((Node)((Node)roads.getChild(i)).getChild(j)).getChild(k);
                    String next = (String)((Properties)(roadObj.getUserData("properties"))).findValue("nextRoad");
                    String prev = (String)((Properties)(roadObj.getUserData("properties"))).findValue("prevRoad");
                    streetInfoMap.put(roadObj.getName(), new StreetInfo((String)((Properties)(roadObj.getUserData("properties"))).findValue("roadName"),next,prev));
                    System.out.println(streetInfoMap.get(roadObj.getName()));
                    
                }
            }
        }
    }
    
    @Override
    public void update(float tpf){
        super.update(tpf);
        
        hud.update(rootNode, app.getCamera(), app.getNifty(), streetInfoMap);
        setSunDir();
        
        rootNode.updateLogicalState(tpf);
        rootNode.updateGeometricState();
        
    }   
    
    protected void setSunDir(){
        Calendar cal = Calendar.getInstance();
        int t=(cal.get(Calendar.HOUR_OF_DAY) * 3600) + (cal.get(Calendar.MINUTE) * 60) + cal.get(Calendar.SECOND);
        float theta= t * (FastMath.PI/43200f);
        float x = -FastMath.cos(theta - (FastMath.PI/2f));
        float y = -FastMath.sin(theta - (FastMath.PI/2f));
        lightDir.set(x, y, 0);
        sun.setDirection(lightDir);
        bsr.setDirection(lightDir.normalizeLocal()); //Update shadow direction
    }
            
    public void onAction(String name, boolean isPressed, float tpf) {
        if (name.equals(MAIN_PAUSE) && isPressed) {          
                    app.getStateManager().detach(app.mainState);
                            
        
                    app.getStateManager().attach(new PauseState());
                    flyCam.unregisterInput();
                    flyCam.setEnabled(false);
                    app.getInputManager().clearMappings();
        }
        
        if (name.equals(MAIN_PAUSE) && !isPressed)
            flyCam.registerWithInput(app.getInputManager());
            
    }
    @Override
    public void stateAttached(AppStateManager stateManager) { }
    
    @Override
    public void stateDetached(AppStateManager stateManager) {       
        flyCam.unregisterInput();
        flyCam.setEnabled(false);
        app.getInputManager().clearMappings();
    }
    
    /**
     * Retrieves root node
     * @return the root node
     *
     */
    public Node getRootNode(){
        return rootNode;
    }
    
    /**
     * Retrieves the FlyCam object
     * @return the FlyCam object
     *
     */
    public FlyByCamera getFlyByCamera() {
        return flyCam;
    }    
}